Inside Solaris logo
The Cobb Group This article is reprinted from the July 1996 issue of  Inside Solaris, a monthly publication of The Cobb Group.

Click for a FREE issue!


Make a shell script mail you a summary

If you often run shell scripts unattended, such as in the background, or via the at command, you may find that it would be helpful if you could get a summary of their operation. In this article, we'll show you how to make a shell script mail you a summary. Using this technique, you'll be able to run your shell scripts, content in the knowledge that when you want to check the results, you can simply look at your mail.

Sending a message with mail

As you probably know, you can send mail to someone by using the mail command. To do so, you give the mail command the recipient list on the command line, and then it accepts a mail message via standard input. Once it finds the end of the input stream, it mails the text to the specified recipients.

Therefore, to have a shell mail us a summary report, we must provide two things: an appropriate recipient list and a message body. Fortunately, both of these are easy to obtain. We'll use your user name as the recipient list and the output of your command list as the message body.

The recipient list

Who is interested in the results of your script file? Presumably, only you. Therefore, in your script file, you can send a message to yourself with the command

$ mail myname

where myname is your user name.

However, this technique has one slight disadvantage. If you give a copy of your script to others, they must edit it to send the results to their mailboxes or you'll get their reports.

Fortunately, there's a better solution. Rather than hard-code your user name into the recipient list, we'll extract your user name from the environment. If you've ever executed the set command and looked at all the environment variables, you may have noticed the one named LOGNAME, which the shell set to your user name. All you need to do in your script is use the LOGNAME environment variable as the recipient, so whoever runs the script gets the report. You can do so like this:

$ mail ${LOGNAME}

The message body

Now that we've properly addressed our summary report, all we need to do is send the mail command the body of the message. If your report merely consists of a mention that the script has run, you can get away with something simple like this:

mail ${LOGNAME} <<!
The test script executed normally.
!

We use a here document (described in the article "Automating Applications that Accept User Input" in last month's issue) to feed a message body to the mail command. One problem with this technique is that you have to know all the reports that you want to send at the time you run your script. That's usually not the case.

The normal case is that you'll want to see all the output of your script in the mail message. That way, you can examine the output for anything unexpected. As you probably suspect, if you have a single command, you can simply pipe its output to the mail command. For example, if you want to find a list of all the files named core in your home directory and below, you might use the command

$ find ~ -name core -print | mail ${LOGNAME}

If you have multiple commands to execute in your script, what do you do? You could try creating a temporary file and appending the output of each command to it. Then, after you mail the results to yourself, you can remove the temporary file. For example, suppose you want a listing of the contents of your .dt/Trash and .wastebasket directories. You could do it like this:

$ ls ~/.dt/Trash >/tmp/test
$ ls ~/.wastebasket >>/tmp/test
$ mail ${LOGNAME} </tmp/test
$ rm /tmp/test

The first line creates the file /tmp/test and fills it with the list of files in your .dt/Trash directory. The second line appends the listing

of your .wastebasket directory to it. The third line mails the resulting file to you and the fourth line deletes the temporary file.

However, this technique has two disadvantages: First, you must create a temporary file that won't be used by some other process. Second, you must be sure to clean up the file when you're finished.

Another way you can find the list of files is to create a shell script that contains the list of files you want to execute. Then you can redirect the output of that shell script to the mail command. For our example, we could create a shell script that does both directory operations, like this:

ls ~/.dt/Trash
ls ~/.wastebasket

Assuming our shell script is named test, we can execute it like this:

$ test | mail ${LOGNAME}

This technique works very well. However, for very simple jobs, it may be too much work. A good method for small command lists is to tell the shell to execute your list of commands in a subshell. Then you can pipe the output of your subshell to the mail command. In effect, it's the same as the technique we just showed you, but you don't need to explicitly create a shell script in a file.

All you need to do with this technique is to put your command list in parentheses, with the pipe symbol outside the parentheses. You can separate your commands with semicolons or newlines. You can process the same example two ways:

$ (ls /; ls ~) | mail ${LOGNAME}

or

$ (ls /
> ls ~) | mail ${LOGNAME}

Capturing error output

So far, we've only shown you how to mail the standard output of a command to yourself. Once you try this technique, you may find that parts of the output you're used to seeing on the console don't arrive in your mail message. This happens because many programs print normal results on the standard output stream, also known as stdout, and errors on the standard error stream, known as stderr.

You may be interested in both the normal output of your commands and the error output. If this is the case, then you'll have to know which shell you're using when you execute your script. If you're using the C shell, you can pipe both the stdout and stderr output to the mail command like this:

$ cmd |& mail ${LOGNAME}

As you can see, all we did was use the |& operator, which tells the C shell to pipe the stderr stream along with the stdout stream. If you're using the Bourne or Korn shell, it's a little more difficult: These shells don't provide the |& operator.

However, you can do the job by telling the shell to redirect the stderr stream to the same place as stdout. Then you can pipe the resulting output to the mail command.

You can merge the stderr and stdout streams by using the I/O redirection command 2>&1, which tells the shell to take the output of stream 2 and send it to the same place as stream 1. Since stream 2 is the stderr stream and stream 1 is the stdout stream, this command does exactly what we want.

Now we're ready to put it all together. Please note that we must use the I/O redirection command that merges the standard error stream to the standard output stream before the pipe symbol, because the pipe symbol separates different sections of the command. If we put the I/O command after the pipe symbol, we'd be telling the shell to take any error output from mail and send it to the standard output. Our completed command line is

$ cmd 2>&1 | mail ${LOGNAME}

Conclusion

If you're as busy as most people, then you may find this trick to be a time-saver. It's nice to be able to run your scripts and programs confident in the ability to examine the results at your leisure rather than watching for the output before it scrolls off the screen. One added benefit is that since the summary is in your mailbox, rather than just on your screen, you can elect to print it, forward a copy to someone else, or just ignore it until you need to look at it again.

 

[The Cobb Group Home Page]

Copyright (c) 1996 The Cobb Group, a division of Ziff-Davis Publishing Company. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis Publishing Company is prohibited. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Publishing Company.

Questions? Comments?